/**
	License:		Artistic license 2.0 (see license.txt, also available online at http://www.perlfoundation.org/artistic_license_2_0)
	Authors:		OrbitalLab (http://www.orbitallab.ru/moonglide/), 2007-2009

	File:			EngParSystem.d
	Description:	particle system
	Date:			07.01.2008 by Jurec
*/
module moonglide.core.engobjects.EngParSystem;

import
	moonglide.util.MoonUtil,
	moonglide.core.engobjects.EngObject,
	moonglide.core.engobjects.EngGeometry,
	moonglide.core.engobjects.EngCamera,

	moonglide.resources.resobjects.lowlevel.ResMesh,
	moonglide.resources.resobjects.lowlevel.ResShader;


struct SEngParticle
{
	float 			size,
					changeSize,
					life,
					angle,
					deltaAngle,
					sortProj,
					speedNorm;

	Vector3f	 	position,
					velocity,
					acceleration;

	Vector4f	 	color,
					changeCol;

	Matrix33f		rotation;

	int 			aim;

	int opCmp( SEngParticle o )
	{
		if( this.sortProj < o.sortProj)
			return -1;

		if( this.sortProj > o.sortProj)
			return 1;

		return 0;
	}
}

enum eparsystalign
{
	to_AxisX,		// align to X coordinate
	to_AxisY,		// align to Y coordinate
	to_AxisZ,		// align to Z coordinate
//	to_Camera_Line,	// align to camera (standart 3d laser line , as from HomeWorld)
	to_Camera		// align to camera (standart 3d sprite)
};

enum eparsysrendertype
{
	points,			// GL_POINTS   -   very simple and fast
	sprites,		// GPU based calculation of screen position without rotation (fast)
	ffp_line,		// line
	sprites_rot,	// GPU based calculation of screen position with rotation (fast)
//  full_gpu,		// GPU based calculation of all attributes of particles
	ffp_sprites		// CPU based without rotation (slow)
};


//=============================================================
/// CEngParSystem - basic particle system for MoonGlide engine
//=============================================================
class CEngParSystem : public CEngGeometry
{
	protected:
		Vector3f		_vv1, _vv2, _vv3, _vv4;

		CResShader	_vertShader_Cam,
						_vertShader_CamRot;

		CEngCamera		_camera;

		void calcSortProj()
		{
			if( _camera )
				for( int ii = 0; ii < parCount; ++ii )
					particles[ii].sortProj = ( particles[ii].position - _camera.wposition ).norm;
		}


		void calcsort_Life()
		{
			for( int ii = 0; ii < parCount; ++ii )
				particles[ii].sortProj = particles[ii].life;
		}


		void generateMesh()
		{
			activeMesh.needToUpdate = true;

			if( sort_FarToNear)
			{
				if( !_camera ) sort_FarToNear = false;


				calcSortProj();
	/*
				debug dbg_log.Write( );
				debug dbg_log.Write( "before:" );
				for( int ii = 0; ii<parCount; ++ii )
				{
					debug dbg_log.Write( particles[ii].sortProj );
				}
	*/
				particles[0..parCount].sort;
	/*
				debug dbg_log.Write( );
				debug dbg_log.Write( "after:" );
				for( int ii = 0; ii<parCount; ++ii )
				{
					debug dbg_log.Write( particles[ii].sortProj );
				}
				debug dbg_log.Write( );*/
			}
			else
				if( sort_Life)
				{
					calcsort_Life();
					particles[0..parCount].sort;
				}

			switch( rendType )
			{
				case eparsysrendertype.points:
					activeMesh.rendType = EMeshRenderType.mrt_point;
					activeMesh.fields["Index"].data.UInt.length = parCount;
					activeMesh.fields["Vertex"].data.Vec3.length = parCount;
					activeMesh.fields["Color"].data.Vec4.length = parCount;

					for( int ii = 0; ii<parCount; ++ii )
					{
						activeMesh.fields["Index"].data.UInt[ii] = ii;
						activeMesh.fields["Vertex"].data.Vec3[ii] = particles[ii].position;
						activeMesh.fields["Color"].data.Vec4[ii] = particles[ii].color;
					}
					break;

				case eparsysrendertype.sprites, eparsysrendertype.sprites_rot:
					activeMesh.rendType = EMeshRenderType.mrt_fill;
					activeMesh.fields["Index"].data.UInt.length = parCount*6;
					activeMesh.fields["Vertex"].data.Vec3.length = parCount*4;
					activeMesh.fields["Normal"].data.Vec3.length = parCount*4;
					activeMesh.fields["Color"].data.Vec4.length = parCount*4;

					if( rendType==eparsysrendertype.sprites_rot)
					{
						activeMesh.fields["BiNormal"].data.Vec3.length = parCount*4;
						activeMesh.fields["Tangent"].data.Vec3.length = parCount*4;
					}

					int i6,i4;
					for( int ii = 0; ii<parCount; ++ii )
					{
						i6 = ii*6;	i4 = ii*4;
						activeMesh.fields["Index"].data.UInt[i6]   = i4;
						activeMesh.fields["Index"].data.UInt[i6+1] = i4+1;
						activeMesh.fields["Index"].data.UInt[i6+2] = i4+3;
						activeMesh.fields["Index"].data.UInt[i6+3] = i4+2;
						activeMesh.fields["Index"].data.UInt[i6+4] = i4+3;
						activeMesh.fields["Index"].data.UInt[i6+5] = i4+1;

						activeMesh.fields["Vertex"].data.Vec3[i4]   = particles[ii].position;
						activeMesh.fields["Vertex"].data.Vec3[i4+1] = particles[ii].position;
						activeMesh.fields["Vertex"].data.Vec3[i4+2] = particles[ii].position;
						activeMesh.fields["Vertex"].data.Vec3[i4+3] = particles[ii].position;

						if( rendType==eparsysrendertype.sprites_rot)
						{
							activeMesh.fields["Tangent"].data.Vec3[i4]   = particles[ii].rotation[0];
							activeMesh.fields["Tangent"].data.Vec3[i4+1] = particles[ii].rotation[0];
							activeMesh.fields["Tangent"].data.Vec3[i4+2] = particles[ii].rotation[0];
							activeMesh.fields["Tangent"].data.Vec3[i4+3] = particles[ii].rotation[0];
							activeMesh.fields["BiNormal"].data.Vec3[i4]   = particles[ii].rotation[1];
							activeMesh.fields["BiNormal"].data.Vec3[i4+1] = particles[ii].rotation[1];
							activeMesh.fields["BiNormal"].data.Vec3[i4+2] = particles[ii].rotation[1];
							activeMesh.fields["BiNormal"].data.Vec3[i4+3] = particles[ii].rotation[1];
						}

						activeMesh.fields["Normal"].data.Vec3[i4].set  (0,particles[ii].size,0);
						activeMesh.fields["Normal"].data.Vec3[i4+1].set(1,particles[ii].size,0);
						activeMesh.fields["Normal"].data.Vec3[i4+2].set(2,particles[ii].size,0);
						activeMesh.fields["Normal"].data.Vec3[i4+3].set(3,particles[ii].size,0);

						activeMesh.fields["Color"].data.Vec4[i4]   = particles[ii].color;
						activeMesh.fields["Color"].data.Vec4[i4+1] = particles[ii].color;
						activeMesh.fields["Color"].data.Vec4[i4+2] = particles[ii].color;
						activeMesh.fields["Color"].data.Vec4[i4+3] = particles[ii].color;
					}
					break;

				case eparsysrendertype.ffp_line:
					activeMesh.rendType = EMeshRenderType.mrt_fill_strip;

					if( parCount<=1)
						break;

					activeMesh.fields["Index"].data.UInt.length = parCount*2;
					activeMesh.fields["Vertex"].data.Vec3.length = parCount*2;
					activeMesh.fields["Color"].data.Vec4.length = parCount*2;
					activeMesh.fields["TexCoord"].data.Vec2.length = parCount*2;

					Vector3f nor = _camera.wposition - particles[0].position;
					nor.normalize;
					Vector3f spe = particles[0].velocity;
					spe.normalize;
					Vector3f _vv = cp(nor, spe)*particles[0].size;

            		activeMesh.fields["Index"].data.UInt[0] = 0;
					activeMesh.fields["Index"].data.UInt[1] = 1;
					activeMesh.fields["Vertex"].data.Vec3[0] = particles[0].position-_vv;
					activeMesh.fields["Vertex"].data.Vec3[1] = particles[0].position+_vv;
					activeMesh.fields["Color"].data.Vec4[0]  = particles[0].color;
					activeMesh.fields["Color"].data.Vec4[1]  = particles[0].color;
					activeMesh.fields["TexCoord"].data.Vec2[0].set(0.0f, 1.0f);
					activeMesh.fields["TexCoord"].data.Vec2[1].set(1.0f, 1.0f);

					bool	TexBool	= true;
					float	tCo		= 0.0f;
					int		i2;

					for( int ii = 1; ii < parCount; ++ii )
					{
					    nor = _camera.wposition - particles[ii].position;
						nor.normalize;

						spe = particles[ii].velocity + particles[ii-1].velocity;
						spe.normalize;

						_vv = cp(nor, spe)*((particles[ii].size + particles[ii-1].size)/2);

						i2 = ii*2;
						activeMesh.fields["Index"].data.UInt[i2]   = i2;
						activeMesh.fields["Index"].data.UInt[i2+1] = i2+1;

						activeMesh.fields["Vertex"].data.Vec3[i2]   = particles[ii].position-_vv;
						activeMesh.fields["Vertex"].data.Vec3[i2+1] = particles[ii].position+_vv;

						activeMesh.fields["Color"].data.Vec4[i2]   = particles[ii].color;
						activeMesh.fields["Color"].data.Vec4[i2+1] = particles[ii].color;

						if( TexBool)
							tCo =1.0f;
						else
							tCo =0.0f;

						TexBool = !TexBool;

						activeMesh.fields["TexCoord"].data.Vec2[i2].set(0.0f, tCo);
						activeMesh.fields["TexCoord"].data.Vec2[i2+1].set(1.0f, tCo);
					}

					break;

				case eparsysrendertype.ffp_sprites:
					activeMesh.rendType = EMeshRenderType.mrt_fill;
					activeMesh.fields["Index"].data.UInt.length = parCount*6;
					activeMesh.fields["Vertex"].data.Vec3.length = parCount*4;
					activeMesh.fields["Color"].data.Vec4.length = parCount*4;
					activeMesh.fields["TexCoord"].data.Vec2.length = parCount*4;

					switch (aligned)
					{
						case eparsystalign.to_Camera:
							_vv1 = _camera.v1;		_vv2 = _camera.v2;
							_vv3 = _camera.v3;		_vv4 = _camera.v4;
							break;

						case eparsystalign.to_AxisX:
							_vv1.set(0,-1,-1);			_vv2.set(0,-1,1);
							_vv3.set(0,1,1);			_vv4.set(0,1,-1);
							break;
						case eparsystalign.to_AxisY:
							_vv1.set(-1,0,1);			_vv2.set(1,0,1);
							_vv3.set(1,0,-1);			_vv4.set(-1,0,-1);
							break;
						case eparsystalign.to_AxisZ:
							_vv1.set(-1,-1,0);			_vv2.set(1,-1,0);
							_vv3.set(1,1,0);			_vv4.set(-1,1,0);
							break;
					}

					int i6,i4;

					for( int ii = 0; ii < parCount; ++ii )
					{
						i6 = ii*6;
						i4 = ii*4;

						activeMesh.fields["Index"].data.UInt[i6]   = i4;
						activeMesh.fields["Index"].data.UInt[i6+1] = i4+1;
						activeMesh.fields["Index"].data.UInt[i6+2] = i4+3;
						activeMesh.fields["Index"].data.UInt[i6+3] = i4+2;
						activeMesh.fields["Index"].data.UInt[i6+4] = i4+3;
						activeMesh.fields["Index"].data.UInt[i6+5] = i4+1;

						activeMesh.fields["Vertex"].data.Vec3[i4]   = particles[ii].position+_vv1*particles[ii].size;
						activeMesh.fields["Vertex"].data.Vec3[i4+1] = particles[ii].position+_vv2*particles[ii].size;
						activeMesh.fields["Vertex"].data.Vec3[i4+2] = particles[ii].position+_vv3*particles[ii].size;
						activeMesh.fields["Vertex"].data.Vec3[i4+3] = particles[ii].position+_vv4*particles[ii].size;

						activeMesh.fields["Color"].data.Vec4[i4]   = particles[ii].color;
						activeMesh.fields["Color"].data.Vec4[i4+1] = particles[ii].color;
						activeMesh.fields["Color"].data.Vec4[i4+2] = particles[ii].color;
						activeMesh.fields["Color"].data.Vec4[i4+3] = particles[ii].color;

				//		activeMesh.fields["Color"].data.Vec4[i4].set(1.0f,0.0f,0.0f,0.3f);//   = particles[ii].Color;
				//		activeMesh.fields["Color"].data.Vec4[i4+1].set(1.0f,0.0f,0.0f,0.4f);// = particles[ii].Color;
				//		activeMesh.fields["Color"].data.Vec4[i4+2].set(1.0f,0.0f,0.0f,0.6f);// = particles[ii].Color;
				//		activeMesh.fields["Color"].data.Vec4[i4+3].set(1.0f,0.0f,1.0f,0.1f);// = particles[ii].Color;

						activeMesh.fields["TexCoord"].data.Vec2[i4].set(0.0f, 1.0f);
						activeMesh.fields["TexCoord"].data.Vec2[i4+1].set(1.0f, 1.0f);
						activeMesh.fields["TexCoord"].data.Vec2[i4+2].set(1.0f, 0.0f);
						activeMesh.fields["TexCoord"].data.Vec2[i4+3].set(0.0f, 0.0f);
					}

					break;
			} // end switch( RendType )
		}


		void generateMat()
		{
			if(( rendType!= eparsysrendertype.points ) && ( material.textures.length == 0 ))
			{
				//NOTE: wtf?
				if( xxTexture != "" )
                    material.newTexture().data ~= CResTextureData( xxTexture, true );
			}

			material.bools["BackCull"] = rendType!= eparsysrendertype.ffp_line ;

			material.bools["Lighting"] = false;


//			material.shader.shaderType =  Shader_ARB ;
			if( rendType == eparsysrendertype.sprites )
				material.shader.setProg( sh_Vertex , _vertShader_Cam );
			else
				if( rendType == eparsysrendertype.sprites_rot )
					material.shader.setProg( sh_Vertex , _vertShader_CamRot );



			material.sourceBlendFactor = EMatBlendSFactor.src_alpha;
			material.destBlendFactor = EMatBlendDFactor.one;
		}

	public:
		char[]			xxTexture="";// tmp string for texture path

		SEngParticle[]	particles;
		int				parCount		= 0,
						poolLen			= 64;	// reserved length

		bool			sort_FarToNear	= true;
		bool			sort_Life		= true;

		eparsysrendertype rendType;// = eparsysrendertype.sprites;

		Vector3f		vector;
		eparsystalign	aligned;

		void delegate(out SEngParticle Par) initPar;

		void setCamera( CEngCamera cam )
		in
		{
			moonAssert( cam  !is null, "camera is null", __FILE__, __LINE__ );
		}
		body
		{
			_camera = cam;
		}

		// Matrix33f[0] - min
		// Matrix33f[1] - center
		// Matrix33f[2] - max
		Matrix33f calculateCenter()
		{
			if( particles.length == 0)
				return Matrix33f.zero;

			Vector3f xMin, xMax;
			xMin = xMax = particles[0].position;

			for( int ii = 1; ii<parCount; ++ii )
			{
				if( xMin.x > particles[ii].position.x )	xMin.x = particles[ii].position.x;
				if( xMin.y > particles[ii].position.y )	xMin.y = particles[ii].position.y;
				if( xMin.z > particles[ii].position.z )	xMin.z = particles[ii].position.z;

				if( xMax.x < particles[ii].position.x )	xMax.x = particles[ii].position.x;
				if( xMax.y < particles[ii].position.y )	xMax.y = particles[ii].position.y;
				if( xMax.z < particles[ii].position.z )	xMax.z = particles[ii].position.z;
			}

			Matrix33f	res;
			res[0] = xMin;
			res[1] = (xMin+xMax)/2;
			res[2] = xMax;
			return res;
		}


		void addParticle()
		{
			if( ints["maxpar"] <= parCount )
				return;

			parCount++;

			if( parCount > particles.length )
				particles.length = particles.length + poolLen;

			if( initPar !is null )
				initPar( particles[parCount-1] );
		}


		void delParticle(int indx)
		{
			if( indx != parCount-1 )
				particles[ indx ] = particles[ parCount-1 ];

			parCount--;
		}


		CEngGeometry getAsGeom()
		{
		    generateMesh();
			generateMat();
			return cast(CEngGeometry)this;
		}

		this( char[] idname  )
		{
			super( idname );
			addLod( CResMesh( "meshPar_" ~ _id ), 100.0f );

			activeMesh = _lods[ 0 ].mesh;
			activeMesh.needUpload = false;

			_activeLevel = 0;
			lodDistance = -1;

			activeMesh.Static( false );

			_vertShader_Cam = CResShader( "data/vp_bill_camera.txt", true );
			_vertShader_CamRot = CResShader( "data/vp_bill_camera_rot.txt", true );

			generateMat();
			parCount = 0;

			floats["rate"]		= 1.0f;
			floats["maxspeed"]	= 0.001f;
			floats["minspeed"]	= 0.001f;
			floats["spread"]	= 0.003f;
			floats["killdist"]	= float.max;
			floats["posspread"]	= 0.0f;
			floats["maxsize"]	= 0.1f;
			floats["minsize"]	= 0.1f;
			floats["lifetime"]  = 1000.0f;

			floats["changeR"]	= 0.0f;
			floats["changeG"]	= 0.0f;
			floats["changeB"]	= 0.0f;
			floats["changeA"]	= 0.0f;

			bools["lifedown"] = true;
			bools["killdist"] = false;

			ints["maxpar"]	= int.max;

			vector.set(1,1,0);
			aligned = eparsystalign.to_Camera;
		}


		~this()
		{
		    debug debugLog.trace( "parsys object destroy begin: " ~ _id );
			//_vertShader_Cam.release;     // if uncommented - then finalization error.. wtf?
		    //_vertShader_CamRot.release;
			particles.length = 0;
		}
}
